﻿using System;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Text;
using System.Web;

namespace ifunction.BaiduBCS.SDK
{
    /// <summary>
    /// Class Extension.
    /// </summary>
    internal static class Extension
    {
        /// <summary>
        /// Checks the null or empty string.
        /// </summary>
        /// <param name="anyString">Any string.</param>
        /// <param name="stringIdentity">The string identity.</param>
        /// <exception cref="System.NullReferenceException"></exception>
        public static void CheckNullOrEmptyString(this string anyString, string stringIdentity)
        {
            if (string.IsNullOrWhiteSpace(anyString))
            {
                throw new NullReferenceException(stringIdentity);
            }
        }

        /// <summary>
        /// Checks the null object.
        /// </summary>
        /// <param name="anyObject">Any object.</param>
        /// <param name="objectIdentity">The object identity.</param>
        /// <exception cref="System.NullReferenceException"></exception>
        public static void CheckNullObject(this object anyObject, string objectIdentity)
        {
            if (anyObject == null)
            {
                throw new NullReferenceException(objectIdentity);
            }
        }

        /// <summary>
        /// Gets the string value.
        /// </summary>
        /// <param name="anyObject">Any object.</param>
        /// <param name="defaultString">The default string.</param>
        /// <returns>System.String.</returns>
        public static string GetStringValue(this string anyObject, string defaultString = "")
        {
            return !string.IsNullOrWhiteSpace(anyObject) ? anyObject : defaultString;
        }

        /// <summary>
        /// Gets the string value.
        /// </summary>
        /// <param name="anyObject">Any object.</param>
        /// <param name="defaultString">The default string.</param>
        /// <returns>System.String.</returns>
        public static string GetStringValue(this object anyObject, string defaultString = "")
        {
            return anyObject != null ? anyObject.ToString() : defaultString;
        }

        /// <summary>
        /// To the URL encoded text.
        /// </summary>
        /// <param name="originalText">The original text.</param>
        /// <returns>System.String.</returns>
        public static string ToUrlEncodedText(this string originalText)
        {
            if (originalText != null)
            {
                originalText = HttpUtility.UrlEncode(originalText, Encoding.UTF8);
            }

            return originalText;
        }

        #region JavaScript Date Time.

        /// <summary>
        /// To the JavaScript date time (milli-seconds).
        /// </summary>
        /// <param name="dateTimeObject">The date time object.</param>
        /// <returns>System.Int64.</returns>
        public static long ToJavaScriptMillisecondsDateTime(this DateTime dateTimeObject)
        {
            return (long)((dateTimeObject - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds);
        }

        /// <summary>
        /// To the JavaScript date time (seconds).
        /// </summary>
        /// <param name="dateTimeObject">The date time object.</param>
        /// <returns>System.Int64.</returns>
        public static long ToJavaScriptSecondsDateTime(this DateTime dateTimeObject)
        {
            return (long)((dateTimeObject - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds);
        }

        /// <summary>
        /// Javas the script long milliseconds to date time.
        /// </summary>
        /// <param name="javaScriptDateTimeTicks">The java script date time ticks (milli-seconds).</param>
        /// <param name="dateTimeKind">Kind of the date time.</param>
        /// <returns>DateTime.</returns>
        public static DateTime JavaScriptLongMillisecondsToDateTime(this long javaScriptDateTimeTicks, DateTimeKind dateTimeKind = DateTimeKind.Utc)
        {
            return new DateTime(1970, 1, 1, 0, 0, 0, dateTimeKind).AddMilliseconds(javaScriptDateTimeTicks);
        }

        /// <summary>
        /// Javas the script long seconds to date time.
        /// </summary>
        /// <param name="javaScriptDateTimeSeconds">The java script date time seconds.</param>
        /// <param name="dateTimeKind">Kind of the date time.</param>
        /// <returns>DateTime.</returns>
        public static DateTime JavaScriptLongSecondsToDateTime(this long javaScriptDateTimeSeconds, DateTimeKind dateTimeKind = DateTimeKind.Utc)
        {
            return new DateTime(1970, 1, 1, 0, 0, 0, dateTimeKind).AddSeconds(javaScriptDateTimeSeconds);
        }

        #endregion

        #region HMAC SHA1

        /// <summary>
        /// To the HMAC SHA1.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="secretKey">The secret key.</param>
        /// <returns>System.String.</returns>
        public static string ToHMACSHA1(this string message, byte[] secretKey)
        {
            using (HMACSHA1 hmac = new HMACSHA1(secretKey))
            {
                return Convert.ToBase64String(
                   hmac.ComputeHash(Encoding.UTF8.GetBytes(message)));
            }
        }

        /// <summary>
        /// To the HMAC SHA1.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="secretKey">The secret key.</param>
        /// <returns>System.String.</returns>
        public static string ToHMACSHA1(this string message, string secretKey)
        {
            using (HMACSHA1 hmac = new HMACSHA1(Encoding.UTF8.GetBytes(secretKey)))
            {
                return Convert.ToBase64String(
                   hmac.ComputeHash(Encoding.UTF8.GetBytes(message)));
            }
        }

        #endregion

        /// <summary>
        /// To the int64.
        /// </summary>
        /// <param name="stringObject">The string object.</param>
        /// <returns>Int64.</returns>
        public static Int64 ToInt64(this string stringObject)
        {
            Int64 result = 0;
            Int64.TryParse(stringObject, out result);
            return result;
        }

        /// <summary>
        /// To boolean.
        /// </summary>
        /// <param name="stringObject">The string object.</param>
        /// <param name="defaultValue">if set to <c>true</c> [default value].</param>
        /// <returns>The boolean result. If failed to concert, return <c>false</c>.</returns>
        public static bool ToBoolean(this string stringObject, bool defaultValue = false)
        {
            bool result = defaultValue;
            if (stringObject == "1")
            {
                result = true;
            }
            else
            {
                Boolean.TryParse(stringObject, out result);
            }

            return result;
        }

        /// <summary>
        /// Reads the stream to bytes.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="closeWhenFinish">if set to <c>true</c> [close when finish].</param>
        /// <returns>System.Byte[][].</returns>
        public static byte[] ReadStreamToBytes(this Stream stream, bool closeWhenFinish = false)
        {
            long originalPosition = 0;

            try
            {
                stream.CheckNullObject("stream");

                if (stream.CanSeek)
                {
                    originalPosition = stream.Position;
                    stream.Position = 0;
                }

                byte[] readBuffer = new byte[4096];

                int totalBytesRead = 0;
                int bytesRead;

                while ((bytesRead = stream.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0)
                {
                    totalBytesRead += bytesRead;

                    if (totalBytesRead == readBuffer.Length)
                    {
                        int nextByte = stream.ReadByte();
                        if (nextByte != -1)
                        {
                            byte[] temp = new byte[readBuffer.Length * 2];
                            Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length);
                            Buffer.SetByte(temp, totalBytesRead, (byte)nextByte);
                            readBuffer = temp;
                            totalBytesRead++;
                        }
                    }
                }

                byte[] buffer = readBuffer;
                if (readBuffer.Length != totalBytesRead)
                {
                    buffer = new byte[totalBytesRead];
                    Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead);
                }
                return buffer;
            }
            finally
            {
                if (stream != null)
                {
                    if (stream.CanSeek)
                    {
                        stream.Position = originalPosition;
                    }

                    if (closeWhenFinish)
                    {
                        stream.Close();
                        stream.Dispose();
                    }
                }

            }
        }

        /// <summary>
        /// To the bytes.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <returns>The <see cref="Byte" />  array of stream.</returns>
        public static byte[] ToBytes(this Stream stream)
        {
            return ReadStreamToBytes(stream, true);
        }

        /// <summary>
        /// Gets the enum contract text.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="enumValue">The enum value.</param>
        /// <returns>System.String.</returns>
        public static string GetEnumContractText<T>(this T enumValue)
            where T : struct
        {
            FieldInfo fieldInfo = typeof(T).GetField(enumValue.ToString());
            EnumMemberAttribute[] attributes = (EnumMemberAttribute[])fieldInfo.GetCustomAttributes(typeof(EnumMemberAttribute), false);

            if (attributes.Length > 0)
                return attributes[0].Value;
            else
                return enumValue.ToString();
        }
    }
}
